#include <stdio.h>
//动态内存管理
//1️⃣malloc和free   vscode 19shunxvbiao2.c
//①malloc
//功能：向内存的堆区申请一块连续可用的空间，并返回指向这块空间的起始地址。
void* malloc(size_t size);
//参数：如果参数size为0，malloc的行为是标准是未定义的，取决于编译器。(单位是字节)
//返回值：
//如果开辟成功，则返回这块空间的起始地址。
//如果开辟失败，则返回一个NULL指针，因此malloc的返回值一定要做检查。
//返回值的类型是void*，所以malloc函数并不知道开辟空间的类型，具体在使用时使用者自己来决定。
//②free
//功能：用来释放动态开辟的内存。
void free(void* prt);
//参数：
//如果参数ptr指向的空间不是动态开辟的，那free函数的行为是未定义的。
//如果参数ptr是NULL指针，则函数什么事都不做。
//free释放空间时，一定要给这块空间的起始位置。
#include <stdlib.h>
int main()
{
    int *p=(int *)malloc(sizeof(int)*INT_MAX);
    if(p==NULL)  //开辟失败  
    {
        perror("错误信息");//错误信息: Not enough space
        return 1;
    }
    free(p);
    //p是野指针，p指向的空间被回收，但p中的值还是那块空间的起始地址。
    p=NULL; //置空的好处：216行（避免重复释放）；350行（避免解引用野指针）
    return 0;
}
#include <stdlib.h>
int main()
{
    int *p=(int *)malloc(20);
    if(p==NULL)  //开辟失败  
    {
        perror("错误信息");
        return 1;
    }
    //开辟成功
    int i;
    for(i=0;i<5;i++)
    {
        p[i]=1;
        printf("%d ",p[i]);//输出：1 1 1 1 1
    }
    free(p);
    p=NULL;
    return 0;
}
//2️⃣calloc和realloc
//③calloc
//功能：为num个大小为size(单位是字节)的元素开辟一块空间，并且把空间的每个字节初始化为0。
void* calloc(size_t num,size_t size);
//与函数malloc的功能区别只在于calloc会在返回地址之前把申请的空间的每个字节初始化为全0。
#include <stdlib.h>
int main()
{
    int*p=(int *)malloc(sizeof(int)*5);
    int i;
    for(i=0;i<5;i++)
    {
        printf("%d\n",p[i]);//输出：-1163005939 (随机值)
    }
    int *q=(int *)calloc(5,sizeof(int));
    for(i=0;i<5;i++)
    {
        printf("%d\n",q[i]);//输出：0
    }
    free(p);
    p=NULL;
    free(q);
    q=NULL;
    return 0;
}
//可以说：calloc==malloc+memset
//④realloc
//功能：对动态开辟内存大小进行调整；也可以直接用来申请空间。
void* realloc(void* prt,size_t size);
//参数：ptr是要调整的内存起始地址；size是调整之后新大小，单位是字节。
//返回值：调整之后的内存起始位置。（两种情况 P120）
#include <stdlib.h>
int main()
{
    int* p=(int *)malloc(sizeof(int)*5);
    if(p==NULL)
    {
        perror("malloc");
        return 1;
    }
    //存放1~5
    int i;
    for(i=0;i<5;i++)
    {
        p[i]=i+1;
    }
    //继续存放6~10，要扩大空间
    //int* p=(int *)realloc(p,sizeof(int)*10);
    //最好不用原指针接收，因为要是开辟失败，连原有空间也找不到了。
    int* q=(int *)realloc(p,sizeof(int)*10);
    if(q==NULL)   //空间调整失败
    { 
        perror("realloc");
        free(p);
        p=NULL;
        return 1;
    }
    p=q;
    for(i=5;i<10;i++)
    {
        p[i]=i+1;
    }
    for(i=0;i<10;i++)
    {
        printf("%d ",p[i]);//输出：1 2 3 4 5 6 7 8 9 10 
    }
    free(p);
    p=NULL;
    return 0;
}
//直接用来申请空间：
#include <stdlib.h>
int main()
{
    int *p=(int *)realloc(NULL,40);//相当于(int *)malloc(40)
    return 0;
}
//3️⃣常见的动态内存的错误
//①对NULL指针的解引用操作 —— 没有对malloc,calloc,realloc函数的返回值做判断
int main()
{
    int *p=(int *)malloc(sizeof(int)*INT_MAX);
    //*p=20;  错误，此时p==NULL
    //改正：
    if(p==NULL)  //开辟失败  
    {
        perror("错误信息");//错误信息: Not enough space
        return 1;
    }
    free(p);
    p=NULL;
    return 0;
}
//②对动态开辟空间的越界访问
int main()
{
    int *p=(int *)malloc(sizeof(int)*5);
    if(p==NULL)  
    {
        perror("错误信息");
        return 1;
    }
    int i;
    /*for(i=0;i<10;i++) 当i>=5时就越界访问了
    {
        p[i]=i;
    }*/
    free(p);
    p=NULL;
    return 0;
}
//③对非动态开辟内存使用free释放
/*
int main()
{
    int a=10;    
    int *p=&a;
    free(p);
    p=NULL;
    return 0;
}*/ 
//程序崩溃了，直到显示未响应才能关掉。
//④使用free释放一块动态开辟内存的一部分
int main()
{
    int *p=(int *)malloc(sizeof(int)*10);
    if(p==NULL)  
    {
        perror("错误信息");
        return 1;
    }
    int i;
    for(i=0;i<5;i++) 
    {
        *p=i;
        p++;
    }
    free(p);//出错
    p=NULL;
    return 0;
}
//去vs2022上演示，会有“噔噔”一声。free释放空间时，一定要给这块空间的起始位置。
//⑤对同一块动态内存多次释放
int* test()
{
    int *p=(int *)malloc(20);
    //……
    free(p);
    //……
    return p;
}
int main()
{
    int *q=test();
    free(q);//二次释放，出错
    q=NULL;
    return 0;
}
int main()
{
    int *p=(int *)malloc(20);
    free(p);
    p=NULL;
    free(p);//这样没问题，因为此时free(NULL)，是NULL指针，则函数什么事都不做。
    p=NULL;
    return 0;
}
//⑥动态开辟内存忘记释放（内存泄漏）
//即使没有free，程序结束也会释放动态开辟内存，但要是程序一直不结束就会发生内存泄漏。
//4️⃣动态内存经典笔试题分析
//①                 见27erchashu.c
#include <stdlib.h>
#include <string.h>
void GetMemory(char *p)
{
    p = (char *)malloc(100);
}
void Test(void)
{
    char *str = NULL;
    GetMemory(str);
    strcpy(str,"hello world");//在这里程序崩溃，因为存在对NULL解引用操作
    printf(str);//等价于printf("%s",str);
}
int main()
{
    Test();
    return 0;
}
//上面的代码是无法打印出hello world的，与27erchashu.c中creatbitree函数二级指针使用类似。
//str是指针变量，本质还是变量，把它传参属于传值的形式，堆区开辟空间的起始地址赋给p，但是并没有传回给str，str还是空指针。
//另外，此代码没有释放开辟的空间，存在内存泄漏。
//有两种方法改正：
//第一种：这个与27erchashu.c中方法一样
#include <stdlib.h>
#include <string.h>
void GetMemory(char **p)
{
    *p = (char *)malloc(100);
}
void Test(void)  
{
    char *str = NULL;
    GetMemory(&str);
    strcpy(str,"hello world");
    printf(str);//输出：hello world
    free(str);
    str=NULL;
}
int main()
{
    Test();
    return 0;
}
//第二种：这个是27erchashu.c中提到因为不支持递归而不能使用的方法，但是可见19shunxvbiao2.c
#include <stdlib.h>
#include <string.h>
char *GetMemory()
{
    char *p = (char *)malloc(100);
    return p; 
}
void Test(void)  
{
    char *str =GetMemory();
    strcpy(str,"hello world");
    printf(str);//输出：hello world
    free(str);
    str=NULL;
}
int main()
{
    Test();
    return 0;
}
//②   见vscode 8.1.c 81行
#include <stdlib.h>
char* GetMemory(void)
{
    char p[]= "hello world"; //栈区数组，进作用域创建，出作用域销毁
    return p;
}
void Test(void)
{
    char *str = NULL;
    str = GetMemory();
    printf(str);
}
int main()
{
    Test();
    return 0;
}
//上面的代码会打印出一堆随机值，与8.1.c中81行指针指向的空间释放一样。
//有两种方法改正：
//第一种：
char* GetMemory(void)
{
    char *p= "hello world";  //p指向常量字符串
    return p;
}
void Test(void)
{
    char *str = NULL;
    str = GetMemory();
    printf(str);//输出：hello world
}
int main()
{
    Test();
    return 0;
}
//第二种：
char* GetMemory(void)
{
    static char p[]= "hello world"; //存放在静态区
    return p;
}
void Test(void)
{
    char *str = NULL;
    str = GetMemory();
    printf(str);//输出：hello world
}
int main()
{
    Test();
    return 0;
}
//③
#include <stdlib.h>
void Test(void)
{
    char *str = (char *)malloc(100);
    strcpy(str, "hello");
    free(str); //str指向的空间释放，但str中存的值还是那块空间的起始地址
    //改正：str=NULL;
    if(str!= NULL)  //这里就是对前面28~30行的强调
    {
        strcpy(str,"world");//对野指针解引用，出错
        printf(str);
    }
}
int main()
{
    Test();
    return 0;
}
//5️⃣柔性数组
//①特点：P121
struct S
{
    int i;
    int a[0]; //柔性数组成员
};
int main()
{
    printf("%zu\n",sizeof(struct S));//输出：4
    return 0;
}
//②使用：
#include <stdlib.h>
struct S
{  
    int n;
    int a[]; //柔性数组成员
};
int main()
{
    //给含柔性数组的结构体分配空间
    struct S*s=(struct S*)malloc(sizeof(struct S) + 10*sizeof(int));
    //                              结构的大小        柔性数组的大小
    if(s==NULL)
    {
        perror("malloc");
        return 1;
    }
    int i;
    for(i=0;i<10;i++)
    {
        s->a[i]=i;
        printf("%d ",s->a[i]);//输出：0 1 2 3 4 5 6 7 8 9 
    }
    //调整含柔性数组的结构体的空间（扩大空间）
    struct S* ps=(struct S*)realloc(s,sizeof(struct S) + 20*sizeof(int));
    if(ps==NULL)
    {
        perror("realloc");
        return 1;
    }
    s=ps;
    for(i=10;i<20;i++)
    {
        s->a[i]=i;
    }
    for(i=0;i<20;i++)
    {
        printf("%d ",s->a[i]);//输出：0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19  
    }
    free(s);
    s=NULL;
    return 0;
}
//③优势：P121
#include <stdlib.h>
struct S
{
    int n;
    int *a;
};
int main()
{
    struct S*s=(struct S*)malloc(sizeof(struct S));
    s->a=(int *)malloc(sizeof(int)*10);
    int i;
    for(i=0;i<10;i++)
    {
        s->a[i]=i;
        printf("%d ",s->a[i]);//输出：0 1 2 3 4 5 6 7 8 9 
    }
    //扩容
    s->a=(int *)realloc(s->a,sizeof(int)*20);
     for(i=10;i<20;i++)
    {
        s->a[i]=i;
    }
    for(i=0;i<20;i++)
    {
        printf("%d ",s->a[i]);//输出：0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19  
    }
    free(s->a);
    free(s);
    s=NULL;
    return 0;
}
//上面这个代码也可以完成柔性数组的效果，可见vscode 19shunxvbiao2.c
//但柔性数组相较于上面的代码有两个好处：P121